﻿/*
 * @Descripttion:
 * @version:
 * @Author: JinYiGao
 * @Date: 2023-01-04 13:12:30
 * @LastEditors: JinYiGao
 * @LastEditTime: 2023-01-04 13:12:30
 */
/*
 * @Descripttion:
 * @version:
 * @Author: JinYiGao
 * @Date: 2022-12-19 17:11:40
 * @LastEditors: JinYiGao
 * @LastEditTime: 2022-12-19 21:00:04
 */

#include <fstream>
#include <iostream>
#include <vector>
#include <string>

#include <tinyxml2/tinyxml2.h>

struct Point {
    float x;
    float y;
};

struct Line {
    Point p1;
    Point p2;
};

// 曲线插值计算t精度
const int seg = 100; // precision = 0.01
// svg图形的宽和高
const int WIDTH = 800;
const int HEIGHT = 600;
// [t,order,points]
std::vector<std::vector<std::vector<Point>>> t_series(seg);
void cal_bezier_point(std::vector<Point> &in_pts, std::vector<Point> &out_pts, const int step) {
    if (in_pts.empty())
        return;
    if (in_pts.size() == 1) {
        out_pts.push_back(in_pts[0]);
        return;
    }
    float t = step * 1.0f / seg;
    std::vector<Point> tmp_in; // 存储针对in_pts经过一次插值计算后得到的线段点
    for (int i = 0; i < in_pts.size() - 1; i++) {
        // TODO
        // 代码填写在下方
        // -----------------------------------
            Point p;
            p.x = in_pts[i].x*(1-t) + in_pts[i+1].x*t;
            p.y = in_pts[i].y*(1-t) + in_pts[i+1].y*t;
            tmp_in.push_back(p);
        // -----------------------------------
    }
    t_series[step].emplace_back(tmp_in);
    cal_bezier_point(tmp_in, out_pts, step);
}

void cal_bezier_curve(std::vector<Point> &in_pts, std::vector<Point> &out_pts) {
    for (int i = 0; i < seg; i++) {
        cal_bezier_point(in_pts, out_pts, i);
    }
}

tinyxml2::XMLDocument *create_svg(const int &width, const int &height) {
    tinyxml2::XMLDocument *doc = new tinyxml2::XMLDocument();

    // 创建 svg 根标签
    tinyxml2::XMLElement *label_svg = doc->NewElement("svg");
    label_svg->SetAttribute("width", width);
    label_svg->SetAttribute("height", height);
    label_svg->SetAttribute("xmlns", "http://www.w3.org/2000/svg");

    doc->InsertEndChild(label_svg);

    return doc;
}

void destroy(tinyxml2::XMLDocument *doc) {
    delete doc;
    doc = nullptr;
}


tinyxml2::XMLElement* make_animation(tinyxml2::XMLDocument &doc,const char* attr,std::vector<float> ps) {
        /*
        <animate attributeName="{attr}"
        attributeType="XML"
        value="{value}"
        begin="0s" dur="10s"
        repeatCount="indefinite"
        ></animate>*/
    tinyxml2::XMLElement *label_line_animation = nullptr;
    label_line_animation = doc.NewElement("animate");
    label_line_animation->SetAttribute("attributeType", "XML");
    label_line_animation->SetAttribute("attributeName", attr);
    std::string path = "";
    for(int p:ps) path += std::to_string(p)+";";
    label_line_animation->SetAttribute("values", path.c_str());
    label_line_animation->SetAttribute("begin", "0s");
    label_line_animation->SetAttribute("dur", "10s");
    label_line_animation->SetAttribute("repeatCount", "indefinite");
    return label_line_animation;
}
inline tinyxml2::XMLElement* make_point(tinyxml2::XMLDocument &doc, const int radius=2, const char *color = "red") {

    tinyxml2::XMLElement *label_point = doc.NewElement("circle");
    // label_point->SetAttribute("cx", 0);
    // label_point->SetAttribute("cy", 0);
    label_point->SetAttribute("r", radius);
    label_point->SetAttribute("fill", color);
    return label_point;
}
inline tinyxml2::XMLElement* make_line(tinyxml2::XMLDocument &doc, const char *color = "black") {

    tinyxml2::XMLElement *label_line = doc.NewElement("circle");
    label_line = doc.NewElement("line");
    // label_line->SetAttribute("x1", 0);
    // label_line->SetAttribute("y1", 0);
    // label_line->SetAttribute("x2", 0);
    // label_line->SetAttribute("y2", 0);
    label_line->SetAttribute("stroke", color);
    return label_line;
}
void write_animation(tinyxml2::XMLDocument &doc) {
    // 创建t = 0 - seg 的 曲线绘制动画
    tinyxml2::XMLElement *label_svg = doc.FirstChildElement("svg");
   
    //  假设阶数=1
    std::vector<float> tx(seg),ty(seg),tx2(seg),ty2(seg);
    tinyxml2::XMLElement* p,* l;
    for (int o = 0; o < t_series.front().size(); o++){
        for (int t = 0; t < 100; t++) {
            tx2[t] = (t_series[t][o][0].x);
            ty2[t] = (HEIGHT - t_series[t][o][0].y);
        }
        p = make_point(doc, 2, o == t_series.front().size()-1?"red":"green");
        p->InsertEndChild(make_animation(doc,"cx",tx2));
        p->InsertEndChild(make_animation(doc,"cy",ty2));
        label_svg->InsertEndChild(p);

        for(int j = 1;j < t_series.front()[o].size();j++){
            std::swap(tx,tx2);
            std::swap(ty,ty2);
            for (int t = 0; t < 100; t++) {
                tx2[t] = (t_series[t][o][j].x);
                ty2[t] = (HEIGHT - t_series[t][o][j].y);
            }
            p = make_point(doc,2,"green");
            l = make_line(doc,"black");
            p->InsertEndChild(make_animation(doc,"cx",tx2));
            p->InsertEndChild(make_animation(doc,"cy",ty2));
            //为线添加动画
            l->InsertEndChild(make_animation(doc,"x1",tx));
            l->InsertEndChild(make_animation(doc,"y1",ty));
            l->InsertEndChild(make_animation(doc,"x2",tx2));
            l->InsertEndChild(make_animation(doc,"y2",ty2));

            label_svg->InsertEndChild(p);
            label_svg->InsertEndChild(l);
        }
    }

    
    // -----------------------------------
}

void write_lines(tinyxml2::XMLDocument &doc, const std::vector<Point> &pts, const char *color = "red") {
    // TODO
    // 代码填写在下方
    // 注意svg坐标为窗口坐标系，需要转换为笛卡尔坐标系
    // -----------------------------------
    tinyxml2::XMLElement *label_svg = doc.FirstChildElement("svg");
    tinyxml2::XMLElement *label_line = nullptr;
    for (int i = 0; i < pts.size() - 1; i++) {
        //  <line x1="0" y1="80" x2="100" y2="20" stroke="black" />
        label_line = make_line(doc,color);
        label_line->SetAttribute("x1", pts[i].x);
        label_line->SetAttribute("y1", HEIGHT - pts[i].y);
        label_line->SetAttribute("x2", pts[i + 1].x );
        label_line->SetAttribute("y2", HEIGHT - pts[i+1].y);
        label_svg->InsertEndChild(label_line);
        label_line = nullptr;
    }
    // -----------------------------------
}

void write_points(tinyxml2::XMLDocument &doc, const std::vector<Point> &pts, const int &point_size,
                  const char *color = "black") {
    tinyxml2::XMLElement *label_svg = doc.FirstChildElement("svg");

    tinyxml2::XMLElement *label_point = nullptr;
    for (int i = 0; i < pts.size(); i++) {
        label_point = make_point(doc, 3 ,color);
        label_point->SetAttribute("cx", pts[i].x);
        label_point->SetAttribute("cy", HEIGHT - pts[i].y);
        label_svg->InsertEndChild(label_point);
        label_point = nullptr;
    }
}

bool save_svg(tinyxml2::XMLDocument &doc, const char *filename) { return doc.SaveFile(filename); }

int main() {
    // 用于贝塞尔曲线计算的原始输入点
    std::vector<Point> points = {{100, 100}, {200, 200}, {300, 100}, {400, 200}};
    //std::vector<Point> points = {{100, 100}, {200, 200}, {150, 250}};
    // 插值曲线结果
    std::vector<Point> out_pts;
    // 计算
    cal_bezier_curve(points, out_pts);

    // -----  svg 绘制 -----
    tinyxml2::XMLDocument *doc = create_svg(WIDTH, HEIGHT);
    write_lines(*doc, points, "blue");
    write_points(*doc, points, 3);
    write_lines(*doc, out_pts, "red");
    write_animation(*doc);
    save_svg(*doc, "test.svg");
    destroy(doc);

    return 0;
}